package com.ar4j.util;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Various collection related utilities
 */
public class CollectionUtils {
      
  private CollectionUtils() {
    // NO CONSTRUCTOR FOR YOU
  }
  
  /**
   * Adds an instance to a multi-map (a map of lists of a specific type), mapped by the given
   * key.
   * 
   * @param <E> type of instance the multi-map holds
   * @param id key of mapping
   * @param map multi-map instance
   * @param instance instance to add
   * @return the list within the multi-map to which the instance was added
   */
  public static <E, F> List<F> addToMapOfLists(E id, Map<E, List<F>> map, F instance) {
    List<F> list = map.get(id);
    if(list == null) {
      list = new ArrayList<F>();
      map.put(id, list);
    }
    
    list.add(instance);
    return list;
  }
  
  /**
   * Adds an instance to a multi-map (a map of sets of a specific type), mapped by the given
   * key.
   * 
   * @param <E> type of instance the multi-map holds
   * @param id key of mapping
   * @param map multi-map instance
   * @param instance instance to add
   * @return the set within the multi-map to which the instance was added
   */
  public static <E, F> Set<F> addToMapOfSets(E id, Map<E, Set<F>> map, F instance) {
    Set<F> list = map.get(id);
    if(list == null) {
      list = new LinkedHashSet<F>();
      map.put(id, list);
    }
    
    list.add(instance);
    return list;
  }
  
  /**
   * Adds an instance to a map-of-maps (a map of maps of a specified type), mapped by the
   * given first and second keys.
   * @param <E> type of first key
   * @param <F> type of second key
   * @param <G> type of instance stored
   * @param map main map-of-maps
   * @param firstKey first key to use
   * @param secondKey second key to use
   * @param instance instance to store
   * @return the inter map (from F to G) that the instance was added to
   */
  public static <E, F, G> Map<F, G> addToMapOfMaps(Map<E, Map<F, G>> map, E firstKey, F secondKey, G instance) {
    Map<F, G> subMap = map.get(firstKey);
    if(subMap == null) {
      subMap = new LinkedHashMap<F, G>();
      map.put(firstKey, subMap);
    }
    
    subMap.put(secondKey, instance);
    return subMap;
  }

  /**
   * Retrieves an element from a map-of-maps.
   * 
   * @param <E> type of first key
   * @param <F> type of second key
   * @param <G> type of instance stored
   * @param map main map-of-maps
   * @param firstKey first key to use
   * @param secondKey second key to use
   * @return the stored instance or null if none is found
   */
  public static <E, F, G> G getFromMapOfMaps(Map<E, Map<F, G>> map, E firstKey, F secondKey) {
    G out = null;
    Map<F, G> subMap = map.get(firstKey);
    if(subMap != null) {
      out = subMap.get(secondKey);
    }
    
    return out;
  }
    
}
